/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package axil.compiler;

import axil.compiler.ast.*;
import axil.compiler.lexer.*;
import axil.compiler.lexer.Token.Type;
import axil.etc.NameValue;
import axil.framework.error.CompilationException;

import static axil.framework.Functions.*;


/**
 * This is the Axil parser. The parser takes the raw stream of lexical tokens
 * and assembles them into a proper graph of AST nodes, checking for basic
 * structural correctness along the way.
 */
public class Parser {
	private Lexer lexer;
	private Token pushed;


	public Parser(Lexer lexer) {
		this.lexer = lexer;
	}


	private Token next() {
		if (pushed != null) {
			Token t = pushed;
			pushed = null;
			return t;
		}
		return lexer.next();
	}


	private void pushback(Token token) {
		if (pushed != null) {
			throw abort("Already pushed " + pushed);
		}
		pushed = token;
	}


	private RuntimeException problem(Token context, String key, NameValue... values) {
		return new CompilationException(context, key, values);
	}


	private RuntimeException syntax(Token context) {
		return problem(context, "syntax-error", nv("that", context));
	}


	private RuntimeException eof(Token context) {
		return problem(context, "unexpected-eof");
	}



	/**
	 * Returns an abstract syntax tree of raw nodes. The graph is only guaranteed
	 * to not be null and not to be structurally defective.
	 */
	public Node parse() {
		Node node = expression();
		Token token = next();
		if (! token.is(Type.EOF)) {
			throw syntax(token);
		}
		return node;
	}


	private Node expression() {
		Token token = next();
		switch(token.type()) {
		case literal:
			return operand(new LiteralNode((Literal)token));

		case keyword:
			return operand(keyword(token));

		case open:
			return operand(subexpression());

		case close:
		case comma:
			throw syntax(token);

		case identifier:
			return operand(identifier((Identifier)token));

		case EOF:
			throw eof(token);

		default:
			throw syntax(token);
		}
	}


	private Node operand(Node node) {
		Token token = next();
		if (token.is(Type.operator)) {
			return op(node, (Op)token, expression());
		}
		pushback(token);
		return node;
	}


	private Node identifier(Identifier ident) {
		Token token = next();
		switch(token.type()) {
		case open:
			return operand(function(ident));

		case close:
		case comma:
			pushback(token);
			return new IdentifierNode(ident);

		case operator:
			return op(new IdentifierNode(ident), (Op)token, expression());

		case EOF:
			return new IdentifierNode(ident);

		default:
			throw syntax(token);
		}
	}


	private FunctionCallNode function(Token func) {
		FunctionCallNode call = new FunctionCallNode((Identifier)func);
		while(true) {
			Token token = next();
			switch (token.type()) {
			case keyword:
			case identifier:
			case literal:
			case open:
				pushback(token);
				call.arg(expression());
				break;

			case close:
				return call;

			case EOF:
				throw eof(token);

			default:
				throw syntax(token);
			}
			token = next();
			switch(token.type()) {
			case comma:
				break;

			case close:
				return call;

			default:
				throw problem(token, "expecting-function-terminator",
				              nv("function", ((Identifier) func).identity()));
			}
		}
	}


	private Node keyword(Token name) {
		return new KeywordNode((Keyword)name);
	}


	private Node op(Node left, Op operator, Node right) {
		return new OperatorNode(left, operator.operator(), right);
	}


	private Node subexpression() {
		Token token = next();
		if (token.is(Type.close)) {
			throw problem(token, "empty-expression");
		}
		pushback(token);
		Node expr = expression();
		token = next();
		if (! token.is(Type.close)) {
			throw problem(token, "expecting-close", nv("this", str(token)));
		}
		return expr;
	}
}
